/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "Creator.h"
#include "Hardware.h"
#include <lib3d/hard/Manager.h>
#include <logs/Log.h>
#include <vector>
#include <sstream>
#include <win32/win32.h>

// Search & Replace 8 -> 9 to get 9 directx support :)
#include <d3d9.h>
#include <registry/registry.h>

namespace liba
{
namespace lib3d
{
namespace hard
{
namespace directx8
{

static HINSTANCE d3d9_dll = 0;
static IDirect3D9 * (WINAPI *direct_create_9) ( UINT SDKVersion ) = 0;
static LPDIRECT3D9 direct3d = 0;
static std::vector<Hardware *> * hardwares;
const char separator = '$';

std::string bite_out(std::string src, unsigned max_size)
{
	for( size_t pos = 0; pos < src.size(); ++pos )
	{
		while( pos != src.size() && (src[pos] <= ' ' || src[pos] > 'z' || src[pos] == separator) )
			src.erase( pos, 1 );
	}
	if( src.size() > max_size )
		src.resize( max_size );
	return src;
}

Creator::Creator()
{
	if( hardwares )
		throw Gluck(__FUNCTION__, "Hardwares already exists");

	std::string user;

	char data[1024];
	DWORD size = sizeof(data);
	if( GetComputerName( data, &size ) )
		user = bite_out( std::string(data, size), 12 );

	std::stringstream os;
	OSVERSIONINFO osver;
	osver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
	if( GetVersionEx( &osver ) )
	{
		os << int(osver.dwMajorVersion) << "." << int(osver.dwMinorVersion) << "." << int(osver.dwBuildNumber);
	}

	std::string proc;
	long mhz = 0;
	win32::registry::read( "HKEY_LOCAL_MACHINE\\HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", "ProcessorNameString", &proc );
	proc = bite_out( proc, 16 );

	win32::registry::read( "HKEY_LOCAL_MACHINE\\HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", "~MHz", &mhz );

	SYSTEM_INFO sys_info;
	GetSystemInfo( &sys_info );

	if( proc.empty() )
	{
		std::stringstream streamProc;
		streamProc << int(sys_info.wProcessorArchitecture) << "." << int(sys_info.wProcessorLevel) << "." << int(sys_info.dwProcessorType) << "." << int(sys_info.wProcessorRevision);
		proc = streamProc.str();
	}


	MEMORYSTATUS stat;
	GlobalMemoryStatus (&stat);

	configuration_data << int(sys_info.dwNumberOfProcessors) << separator;
	configuration_data << proc << separator;
	configuration_data << mhz << separator;
	configuration_data << int(stat.dwTotalPhys / 1024 / 1024) << separator;
	configuration_data << os.str() << separator;
	configuration_data << user << separator;
	configuration_data << GetSystemMetrics(SM_CXFULLSCREEN) << separator;

	hardwares = new std::vector<Hardware *>;

	d3d9_dll = LoadLibrary("D3D9.DLL");
	if( !d3d9_dll )
	{
		logs::log() << logs::error << "In lib3d::hard::directx8::create(): No D3D9.dll\n";
	}

	direct_create_9 = (IDirect3D9 * (WINAPI *) ( UINT SDKVersion ))GetProcAddress (d3d9_dll, "Direct3DCreate9");
	if ( !direct_create_9 )
	{
		configuration_data << "No Direct3DCreate9" << separator;
		logs::log() << logs::error << "In lib3d::hard::directx8::create(): DirectX is lower than 9\n";
		return ;
	}

	if( !(direct3d = direct_create_9(D3D_SDK_VERSION )) )
	{
		configuration_data << "Bad Direct3DCreate9" << separator;
		logs::log() << logs::error << "In In lib3d::hard::directx8::create(): Failed to create Direct8 instance\n";
		return ;
	}

#ifdef LIB3D_USE_PERFHUD
	createHardwaresPerfHud();
#else
	createHardwaresRegular();
#endif
}

void Creator::createHardwaresRegular()
{
	UINT adapter_count = direct3d->GetAdapterCount();
	
	for( UINT i = 0; i < adapter_count; ++i )
	{
		AdapterInfo ai;
		ai.AdapterNumber = i;
		ai.DeviceType = D3DDEVTYPE_HAL;

		std::map<std::string, int> caps_map = GetCapsMap(ai);
		if (caps_map.empty())
			continue;

		for( std::map<std::string,int>::const_iterator it = caps_map.begin(); it != caps_map.end(); ++it )
		{
			logs::log() << it->first << " = " << it->second << "\n";
		}

		try
		{
			Hardware * ha = new Hardware( direct3d, ai, caps_map );
			hardwares->push_back( ha );
			hard::Manager::add_hardware( ha );

			configuration_data << bite_out(ha->get_hardware_name(), 32) << separator;
			configuration_data << "T" << separator;
		}
		catch( const Gluck & glu)
		{
			logs::log() << "Gluck while creating hardware: " << glu.get_description() << " at: [" << glu.get_place() << "]\n";
		}
	}
///	logs::log() << "config:{" << configuration_data << "}\n";
}

void Creator::createHardwaresPerfHud()
{
	UINT adapterCount= direct3d->GetAdapterCount();

	AdapterInfo ai;
	ai.AdapterNumber = 0xFFFFFFFF;
	ai.DeviceType = D3DDEVTYPE_HAL;

	for (UINT adapter = 0; adapter < adapterCount; ++adapter)
	{
		D3DADAPTER_IDENTIFIER9 identifier;
		direct3d->GetAdapterIdentifier(adapter, 0, &identifier);
		LOG_INFO("Direct3D", "Probing adapter %u : %s", adapter, identifier.Description);
		std::string descr = std::string(identifier.Description);
		if (descr.find("PerfHUD") != std::string::npos)
		{
			ai.AdapterNumber = adapter;
			ai.DeviceType = D3DDEVTYPE_REF;
			break;
		}
	}

	if (ai.AdapterNumber == 0xFFFFFFFF)
	{
		LOG_ERROR("Direct3D", "Failed to find PerfHUD hardware. Falling back to default method");
		createHardwaresRegular();
	}
	else
	{
		std::map<std::string, int> caps_map = GetCapsMap(ai);
		if (caps_map.empty())
		{
			LOG_FATAL("Direct3D", "Failed to enumerate PerfHUD device caps");
			THROW(Gluck(__FUNCTION__, "Failed to enumerate PerfHUD device caps"));
		}

		Hardware * ha = new Hardware( direct3d, ai, caps_map );
		hardwares->push_back( ha );
		hard::Manager::add_hardware( ha );
	}
}

std::map<std::string, int> Creator::GetCapsMap(const AdapterInfo& ai)
{
	D3DCAPS9 device_caps;

	if( direct3d->GetDeviceCaps( ai.AdapterNumber, ai.DeviceType, &device_caps ) != D3D_OK )
	{
		LOG_ERROR("Direct3D", "Failed to get caps");
		return std::map<std::string, int>();
	}
//		if( device_caps.MaxPointSize == 0 )
	logs::log() << "Examining device capabilities\n";
	logs::log() << "--------------------------------------------------------------\n";

	std::map<std::string,int> caps_map;

	caps_map["device_caps.MaxPointSize"] = int(device_caps.MaxPointSize);
	caps_map["device_caps.VertexShaderVersion"] = device_caps.VertexShaderVersion & 0xffff;
	caps_map["device_caps.PixelShaderVersion"] = device_caps.PixelShaderVersion & 0xffff;
	caps_map["device_caps.MaxVertexShaderConst"] = device_caps.MaxVertexShaderConst;
	caps_map["device_caps.MaxAnisotropy"] = device_caps.MaxAnisotropy;
	caps_map["device_caps.MaxSimultaneousTextures"] = device_caps.MaxSimultaneousTextures;
	caps_map["device_caps.MaxTextureBlendStages"] = device_caps.MaxTextureBlendStages;
	caps_map["device_caps.MaxVertexIndex"] = device_caps.MaxVertexIndex;
	caps_map["device_caps.MaxPrimitiveCount"] = device_caps.MaxPrimitiveCount;
	caps_map["device_caps.MaxTextureWidth"] = device_caps.MaxTextureWidth;
	caps_map["device_caps.MaxTextureHeight"] = device_caps.MaxTextureHeight;
	caps_map["device_caps.MaxVolumeExtent"] = device_caps.MaxVolumeExtent;
	caps_map["device_caps.MaxTextureRepeat"] = device_caps.MaxTextureRepeat;
	caps_map["device_caps.MaxTextureAspectRatio"] = device_caps.MaxTextureAspectRatio;

//9		caps_map["device_caps.Caps2.D3DCAPS2_CANAUTOGENMIPMAP"] = (device_caps.Caps2 & D3DCAPS2_CANAUTOGENMIPMAP) != 0;
	caps_map["device_caps.DevCaps.D3DDEVCAPS_HWRASTERIZATION"] = (device_caps.DevCaps & D3DDEVCAPS_HWRASTERIZATION) != 0;
	caps_map["device_caps.DevCaps.D3DDEVCAPS_HWTRANSFORMANDLIGHT"] = (device_caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) != 0;
	caps_map["device_caps.PrimitiveMiscCaps.D3DPMISCCAPS_MASKZ"] = (device_caps.PrimitiveMiscCaps & D3DPMISCCAPS_MASKZ) != 0;
	caps_map["device_caps.PrimitiveMiscCaps.D3DPMISCCAPS_CULLNONE"] = (device_caps.PrimitiveMiscCaps & D3DPMISCCAPS_CULLNONE) != 0;
	caps_map["device_caps.PrimitiveMiscCaps.D3DPMISCCAPS_CULLCW"] = (device_caps.PrimitiveMiscCaps & D3DPMISCCAPS_CULLCW) != 0;
	caps_map["device_caps.PrimitiveMiscCaps.D3DPMISCCAPS_CULLCCW"] = (device_caps.PrimitiveMiscCaps & D3DPMISCCAPS_CULLCCW) != 0;
	caps_map["device_caps.PrimitiveMiscCaps.D3DPMISCCAPS_COLORWRITEENABLE"] = (device_caps.PrimitiveMiscCaps & D3DPMISCCAPS_COLORWRITEENABLE) != 0;
	caps_map[":) device_caps.PrimitiveMiscCaps.D3DPMISCCAPS_BLENDOP"] = (device_caps.PrimitiveMiscCaps & D3DPMISCCAPS_BLENDOP) != 0;
//9		caps_map[":) device_caps.PrimitiveMiscCaps.D3DPMISCCAPS_SEPARATEALPHABLEND"] = (device_caps.PrimitiveMiscCaps & D3DPMISCCAPS_SEPARATEALPHABLEND) != 0;
	caps_map[":) device_caps.RasterCaps.D3DPRASTERCAPS_ZTEST"] = (device_caps.RasterCaps & D3DPRASTERCAPS_ZTEST) != 0;
	caps_map["device_caps.ZCmpCaps.D3DPCMPCAPS_ALWAYS"] = (device_caps.ZCmpCaps & D3DPCMPCAPS_ALWAYS) != 0;
	caps_map["device_caps.ZCmpCaps.D3DPCMPCAPS_LESS"] = (device_caps.ZCmpCaps & D3DPCMPCAPS_LESS) != 0;
	caps_map["device_caps.ZCmpCaps.D3DPCMPCAPS_LESSEQUAL"] = (device_caps.ZCmpCaps & D3DPCMPCAPS_LESSEQUAL) != 0;
//		caps_map["device_caps.ZCmpCaps.D3DPCMPCAPS_NEVER"] = (device_caps.ZCmpCaps & D3DPCMPCAPS_NEVER) != 0;
	caps_map["device_caps.SrcBlendCaps.D3DPBLENDCAPS_ONE"] = (device_caps.SrcBlendCaps& D3DPBLENDCAPS_ONE) != 0;
	caps_map["device_caps.SrcBlendCaps.D3DPBLENDCAPS_SRCALPHA"] = (device_caps.SrcBlendCaps & D3DPBLENDCAPS_SRCALPHA) != 0;
	caps_map["device_caps.DestBlendCaps.D3DPBLENDCAPS_ONE"] = (device_caps.DestBlendCaps & D3DPBLENDCAPS_ONE) != 0;
	caps_map["device_caps.DestBlendCaps.D3DPBLENDCAPS_INVSRCALPHA"] = (device_caps.DestBlendCaps & D3DPBLENDCAPS_INVSRCALPHA) != 0;
	caps_map["device_caps.AlphaCmpCaps.D3DPCMPCAPS_EQUAL"] = (device_caps.AlphaCmpCaps & D3DPCMPCAPS_EQUAL) != 0;
	caps_map["device_caps.AlphaCmpCaps.D3DPCMPCAPS_NOTEQUAL"] = (device_caps.AlphaCmpCaps & D3DPCMPCAPS_NOTEQUAL) != 0;
	caps_map["device_caps.TextureCaps.D3DPTEXTURECAPS_ALPHA"] = (device_caps.TextureCaps & D3DPTEXTURECAPS_ALPHA) != 0;
	caps_map["device_caps.TextureCaps.D3DPTEXTURECAPS_CUBEMAP"] = (device_caps.TextureCaps & D3DPTEXTURECAPS_CUBEMAP) != 0;
	caps_map["device_caps.TextureCaps.D3DPTEXTURECAPS_CUBEMAP_POW2"] = (device_caps.TextureCaps & D3DPTEXTURECAPS_CUBEMAP_POW2) != 0;
	caps_map["device_caps.TextureCaps.D3DPTEXTURECAPS_MIPCUBEMAP"] = (device_caps.TextureCaps & D3DPTEXTURECAPS_MIPCUBEMAP) != 0;
	caps_map["device_caps.TextureCaps.D3DPTEXTURECAPS_MIPMAP"] = (device_caps.TextureCaps & D3DPTEXTURECAPS_MIPMAP) != 0;
	caps_map["device_caps.TextureCaps.D3DPTEXTURECAPS_MIPVOLUMEMAP"] = (device_caps.TextureCaps & D3DPTEXTURECAPS_MIPVOLUMEMAP) != 0;
	caps_map["device_caps.TextureCaps.D3DPTEXTURECAPS_SQUAREONLY"] = (device_caps.TextureCaps & D3DPTEXTURECAPS_SQUAREONLY) != 0;
	caps_map["device_caps.TextureCaps.D3DPTEXTURECAPS_POW2"] = (device_caps.TextureCaps & D3DPTEXTURECAPS_POW2) != 0;
	caps_map["device_caps.TextureCaps.D3DPTEXTURECAPS_VOLUMEMAP"] = (device_caps.TextureCaps & D3DPTEXTURECAPS_VOLUMEMAP) != 0;
	caps_map["device_caps.TextureCaps.D3DPTEXTURECAPS_VOLUMEMAP_POW2"] = (device_caps.TextureCaps & D3DPTEXTURECAPS_VOLUMEMAP_POW2) != 0;
	caps_map["device_caps.TextureAddressCaps.D3DPTADDRESSCAPS_CLAMP"] = (device_caps.TextureAddressCaps & D3DPTADDRESSCAPS_CLAMP) != 0;
	caps_map["device_caps.TextureAddressCaps.D3DPTADDRESSCAPS_WRAP"] = (device_caps.TextureAddressCaps & D3DPTADDRESSCAPS_WRAP) != 0;
	caps_map["device_caps.TextureAddressCaps.D3DPTADDRESSCAPS_MIRROR"] = (device_caps.TextureAddressCaps & D3DPTADDRESSCAPS_MIRROR) != 0;
	caps_map["device_caps.TextureAddressCaps.D3DPTADDRESSCAPS_MIRRORONCE"] = (device_caps.TextureAddressCaps & D3DPTADDRESSCAPS_MIRRORONCE) != 0;
	caps_map["device_caps.VolumeTextureAddressCaps.D3DPTADDRESSCAPS_CLAMP"] = (device_caps.VolumeTextureAddressCaps & D3DPTADDRESSCAPS_CLAMP) != 0;
	caps_map["device_caps.VolumeTextureAddressCaps.D3DPTADDRESSCAPS_WRAP"] = (device_caps.VolumeTextureAddressCaps & D3DPTADDRESSCAPS_WRAP) != 0;
	caps_map["device_caps.VolumeTextureAddressCaps.D3DPTADDRESSCAPS_MIRROR"] = (device_caps.VolumeTextureAddressCaps & D3DPTADDRESSCAPS_MIRROR) != 0;
	caps_map["device_caps.VolumeTextureAddressCaps.D3DPTADDRESSCAPS_MIRRORONCE"] = (device_caps.VolumeTextureAddressCaps & D3DPTADDRESSCAPS_MIRRORONCE) != 0;
	caps_map["device_caps.StencilCaps.D3DSTENCILCAPS_DECRSAT"] = (device_caps.StencilCaps & D3DSTENCILCAPS_DECRSAT) != 0;
	caps_map["device_caps.StencilCaps.D3DSTENCILCAPS_DECR"] = (device_caps.StencilCaps & D3DSTENCILCAPS_DECR) != 0;
	caps_map["device_caps.StencilCaps.D3DSTENCILCAPS_INCRSAT"] = (device_caps.StencilCaps & D3DSTENCILCAPS_INCRSAT) != 0;
	caps_map["device_caps.StencilCaps.D3DSTENCILCAPS_INCR"] = (device_caps.StencilCaps & D3DSTENCILCAPS_INCR) != 0;
	caps_map["device_caps.StencilCaps.D3DSTENCILCAPS_KEEP"] = (device_caps.StencilCaps & D3DSTENCILCAPS_KEEP) != 0;
	caps_map[":) device_caps.FVFCaps.D3DFVFCAPS_PSIZE"] = (device_caps.FVFCaps & D3DFVFCAPS_PSIZE) != 0;
	caps_map["device_caps.TextureOpCaps.D3DTEXOPCAPS_MODULATE"] = (device_caps.TextureOpCaps & D3DTEXOPCAPS_MODULATE) != 0;
	caps_map["device_caps.TextureOpCaps.D3DTEXOPCAPS_MODULATE2X"] = (device_caps.TextureOpCaps & D3DTEXOPCAPS_MODULATE2X) != 0;
	caps_map["device_caps.TextureOpCaps.D3DTEXOPCAPS_MULTIPLYADD"] = (device_caps.TextureOpCaps & D3DTEXOPCAPS_MULTIPLYADD) != 0;
	caps_map["device_caps.TextureOpCaps.D3DTEXOPCAPS_BLENDTEXTUREALPHA"] = (device_caps.TextureOpCaps & D3DTEXOPCAPS_BLENDTEXTUREALPHA) != 0;
//9		caps_map["device_caps.DeclTypes.D3DDTCAPS_UBYTE4"] = (device_caps.DeclTypes & D3DDTCAPS_UBYTE4) != 0;

	return caps_map;
}

Creator::~Creator()
{
	while( !hardwares->empty() )
	{
		Manager::remove_hardware( hardwares->back() );
		delete hardwares->back();
		hardwares->pop_back();
	}

	logs::log() << "Deleting hardwares\n";
	delete hardwares; hardwares = 0;

	int ref = 0;
	if( direct3d )
	{
		logs::log() << "direct3d->Release()\n";
		ref = direct3d->Release(); direct3d = 0;
	}
	if( d3d9_dll )
	{
		logs::log() << "FreeLibrary( d3d8_dll )\n";
		FreeLibrary( d3d9_dll ); d3d9_dll = 0;
	}
}

void reload_hardware()
{
	for(size_t i = 0; i < hardwares->size(); ++i)
	{
		Hardware * ha = (*hardwares)[i];
		Manager::remove_hardware( ha ); 
		Manager::add_hardware( ha ); 
	}
}

} // namespace directx8
} // namespace hard
} // namespace lib3d
} // namespace liba

